#pragma once
//使用select 单进程 多路转接
#include <iostream>
#include <string>
#include <memory>
#include <sys/select.h>
#include "Log.hpp"
#include "Socket.hpp"

using namespace SocketModule;
using namespace LogModule;

#define NUM sizeof(fd_set) * 8

const int gdefaultfd = -1;

//最开始的时候, tcpserver只有一个listensockfd

class SelectServer
{
public:
    SelectServer(int port)
        :_port(port),
        _listen_socket(std::make_unique<TcpSocket>()),
        _isrunning(false)
    {}
    //创建listen套接字, 并把fd放到辅助数组中
    void Init()
    {
        _listen_socket->BuildTcpSocketMethod(_port);
        //初始化辅助数组
        for(int i = 0; i < NUM; i++)
            _fd_array[i] = gdefaultfd;
        //把监听套接字添加进去
        _fd_array[0] = _listen_socket->Fd();
    }
    //开始使用select轮询检测是否有文件描述符就绪, timeout表示阻塞等待时间
    /*
                        读文件描述符集(只关心是否可读) 写文件描述符集 异常文件描述符集合   秒/微妙 返回剩余时间
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    // 最大文件描述符值+1   输入输出型参数
    */
    void Loop()
    {
        fd_set rfds;
        _isrunning  = true;
        while(_isrunning)
        {
            //清空rfds
            FD_ZERO(&rfds);
            struct timeval timeout = {10, 0};
            int maxfd = gdefaultfd;

            //遍历辅助数组,添加文件描述符到读文件描述符集
            for(int i = 0; i < NUM; i++)
            {
                if(_fd_array[i] == gdefaultfd)
                    continue;
                //合法的fd
                FD_SET(_fd_array[i], &rfds); //包含listen套接字
                //更新最大值
                if(maxfd < _fd_array[i])
                {
                    maxfd = _fd_array[i];
                }
            }
            //让select替代accept阻塞等待检测新连接的到来
            //让select负责读写事件是否就绪的等待
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
            switch(n)
            {
                case 0:
                    std::cout << "time out..." << std::endl;
                case -1:
                    perror("select");
                default:
                    //有事件就绪了
                    //交给对用的模块进行处理
                    std::cout << "有事件就绪了..., timeout: " << timeout.tv_sec << ":" << timeout.tv_usec << std::endl; 
                    Dispatcher(rfds);
            }
        }
    }

    void Dispatcher(fd_set &rfds)
    {
        for(int i = 0; i < NUM; i++)
        {
            //判断辅助数组中有哪些文件描述符就绪
            if(_fd_array[i] == gdefaultfd)
                continue;
            //出现合法文件描述符
            if(_fd_array[i] == _listen_socket->Fd())
            {
                //如果存在于rfds
                if(FD_ISSET(_fd_array[i], &rfds))
                {
                    Accepter(); //连接的获取
                }
            }
            else
            {
                if(FD_ISSET(_fd_array[i], &rfds))
                {
                    Recver(i); //IO的处理
                }
            }
        }
    }

    //获取新连接
    void Accepter()
    {
        InetAddr client;
        int newfd = _listen_socket->Accepter(&client); //不会被阻塞
        if(newfd < 0)
            return;
        else
        {
            std::cout << "获得了一个新的链接: " << newfd << "client info: " << client.Addr() << std::endl;
            //recv()此时新链接建立成功,我们并不清楚读事件是否就绪,让select帮我关心
            int pos = -1;
            for(int j = 0; j < NUM; j++)
            {
                if(_fd_array[j] == gdefaultfd)
                {
                    pos = j;
                    break;
                }
            }
            if(pos == -1)
            {
                LOG(LogLevel::ERROR) << "服务器连接数量已满...";
                close(newfd);
            }
            else
            {
                _fd_array[pos] = newfd;
            }
        }
    }

    //读事件就绪
    void Recver(int who)
    {
        //这里的recv不完善,必须得有协议
        char buffer[1024];
        ssize_t n = recv(_fd_array[who], buffer, sizeof(buffer) - 1, 0);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "client# " << buffer << std::endl;
            //把读到的信息, 再回显回去
            std::string message = "echo# ";
            message += buffer;
            send(_fd_array[who], message.c_str(), message.size(), 0);
        }
        else if(n == 0)
        {
            LOG(LogLevel::DEBUG) << "客户端退出, sockfd: " << _fd_array[who];
            close(_fd_array[who]);
            _fd_array[who] = gdefaultfd;
        }
        else
        {
            LOG(LogLevel::DEBUG) << "客户端读取错误, sockfd: " << _fd_array[who];
            close(_fd_array[who]);
            _fd_array[who] = gdefaultfd;
        }
    }
    ~SelectServer(){}
private:
    uint16_t _port;
    std::unique_ptr<Socket> _listen_socket;
    bool _isrunning;
    int _fd_array[NUM]; //辅助数组
};